<

Flutter アーキテクチャの概要

この記事は、のアーキテクチャの高レベルの概要を提供することを目的としています。 Flutter には、その設計を形成する中心的な原則と概念が含まれます。

Flutter は、コードの再利用を可能にするように設計されたクロスプラットフォーム UI ツールキットです。 iOS や Android などのオペレーティング システム間で利用できると同時に、 アプリケーションは、基盤となるプラットフォーム サービスと直接インターフェイスします。目標 開発者が自然な操作性を備えた高性能アプリを提供できるようにすることです。 異なるプラットフォームで、違いが存在する場合はそれを受け入れながら、 できるだけ多くのコードを。

開発中、Flutter アプリはステートフル ホット リロードを提供する VM で実行されます。 完全な再コンパイルを必要とせずに変更できます。リリースに向けて、Flutter アプリがコンパイルされます Intel x64 命令でも ARM 命令でも、マシンコードに直接、または Web を対象とする場合は JavaScript。フレームワークはオープンソースであり、寛容な BSD ライセンスを持ち、サードパーティ パッケージの活発なエコシステムを持っています。 コアライブラリの機能を補完します。

この概要はいくつかのセクションに分かれています。

  1. レイヤーモデル: Flutter を構築するための要素。
  2. リアクティブなユーザーインターフェイス: Flutter ユーザー インターフェイスの中心的な概念 発達。
  3. の紹介ウィジェット: Flutter ユーザーの基本的な構成要素 インターフェース。
  4. レンダリングプロセス: Flutter が UI コードをピクセルに変換する方法。
  5. の概要プラットフォームエンベダー: モバイルと デスクトップ OS は Flutter アプリを実行します。
  6. Flutter と他のコードの統合: さまざまなテクニックに関する情報 Flutter アプリで利用できます。
  7. ウェブのサポート: の特徴についての結論 ブラウザ環境で flutterします。

アーキテクチャレイヤー

Flutter は、拡張可能な階層化システムとして設計されています。シリーズとして存在しています それぞれが基礎となる層に依存する独立したライブラリ。レイヤーがありません 下のレイヤーへの特権アクセス、およびフレームワーク レベルのすべての部分が オプションで交換できるように設計されています。

Architectural
diagram

Flutter アプリケーションは、基盤となるオペレーティング システムに対してパッケージ化されています。 他のネイティブ アプリケーションと同じように。プラットフォーム固有のエンベッダーが提供するもの エントリーポイント。基盤となるオペレーティング システムと連携してアクセスします。 レンダリング サーフェス、アクセシビリティ、入力などのサービス。そして、 メッセージイベントループ。エンベッダーは適切な言語で書かれています プラットフォーム: 現在、Android の場合は Java および C++、Objective-C/Objective-C++ iOS および macOS の場合、および C++ の場合Windows と Linux。エンベッダー Flutter を使用する コードはモジュールとして既存のアプリケーションに統合できます。または、コードは アプリケーションのコンテンツ全体になります。 Flutter には多数のエンベッダーが含まれています 一般的なターゲット プラットフォームの場合、他のエンベダーも 存在。

Flutter の中核となるのは、 flutterエンジン、 これは主に C++ で書かれており、 すべての Flutter アプリケーションをサポートするために必要なプリミティブ。 このエンジンは、合成されたシーンのラスタライズを担当します。 新しいフレームを塗装する必要があるときはいつでも。 Flutter のコア API の低レベル実装を提供します。 グラフィックスを含む(インペラiOS 上で Android にも登場する予定です。 とスキア他のプラットフォームでは) テキストレイアウト、 ファイルおよびネットワーク I/O、アクセシビリティのサポート、 プラグイン アーキテクチャと Dart ランタイム そしてツールチェーンをコンパイルします。

エンジンは、次の方法で Flutter フレームワークに公開されます。dart:ui、 これにより、基礎となる C++ コードが Dart クラスにラップされます。この図書館 入力を駆動するためのクラスなどの最下位レベルのプリミティブを公開します。 グラフィックスおよびテキスト レンダリング サブシステム。

通常、開発者は次を通じて Flutter と対話します。 flutterフレームワーク、 これは、Dart 言語で書かれた最新のリアクティブなフレームワークを提供します。それ プラットフォーム、レイアウト、基本ライブラリの豊富なセットが含まれており、以下で構成されます。 一連のレイヤー。下から上に作業すると、次のようになります。

  • 基本基礎的なクラスやビルディング ブロック サービスなどアニメーション、ペインティング、 とジェスチャーそのオファー 基礎となる基盤の上で一般的に使用される抽象化。
  • レンダリング 層を提供します レイアウトを扱うための抽象化。このレイヤーを使用すると、次のツリーを構築できます。 レンダリング可能なオブジェクト。これらのオブジェクトを動的に操作するには、 ツリーは、変更を反映してレイアウトを自動的に更新します。
  • ウィジェットレイヤーは 構成の抽象化。レンダリング レイヤ内の各レンダー オブジェクトには、 ウィジェット層の対応するクラス。さらに、ウィジェットレイヤー を使用すると、再利用できるクラスの組み合わせを定義できます。これは リアクティブ プログラミング モデルが導入される層。
  • 材料クパチーノライブラリは、ウィジェット レイヤーのを使用する包括的なコントロールのセットを提供します。 マテリアルまたは iOS デザイン言語を実装するための合成プリミティブ。

Flutter フレームワークは比較的小さいです。多くの高レベルの機能 開発者が使用する可能性のあるものは、プラットフォーム プラグインを含むパッケージとして実装されます 好きカメラとウェブビュー、プラットフォームに依存しない のような機能キャラクター、http、 とアニメーションコア Dart に基づいて構築されており、 flutterライブラリ。これらのパッケージの中には、より広範なエコシステムからのものもあります。 などのサービスをカバーアプリ内 支払い、りんご 認証、 とアニメーション。

この概要の残りの部分では、レイヤーを下に向かって幅広くナビゲートします。 UI開発のリアクティブパラダイム。次に、ウィジェットがどのように構成されるかを説明します。 一緒に、オブジェクトの一部としてレンダリングできるオブジェクトに変換されます。 応用。 Flutter がプラットフォームで他のコードとどのように相互運用するかを説明します Flutter の Web サポートがどのように異なるかを簡単に説明する前に、レベルについて説明します。 他のターゲット。

アプリの構造

次の図は、各部分の概要を示しています。 によって生成された通常の Flutter アプリを構成します。flutter create。 Flutter Engine がこのスタックのどこに位置するかを示します。 API の境界を強調表示し、リポジトリを識別します 個々の作品が存在する場所。以下の凡例がそれを明確にします を説明するために一般的に使用される用語の一部 Flutter アプリの一部。

The layers of a Flutter app created by "flutter create": Dart app, framework, engine, embedder, runner

ダーツアプリ

  • ウィジェットを目的の UI に合成します。
  • ビジネスロジックを実装します。
  • アプリ開発者が所有。

フレームワーク(ソースコード)

  • 高品質なアプリを構築するための高レベルの API を提供します (例: ウィジェット、ヒットテスト、ジェスチャー検出、 アクセシビリティ、テキスト入力)。
  • アプリのウィジェット ツリーをシーンに合成します。

エンジン(ソースコード)

  • 合成されたシーンのラスタライズを担当します。
  • Flutter のコア API の低レベル実装を提供します (グラフィックス、テキスト レイアウト、Dart ランタイムなど)。
  • を使用してその機能をフレームワークに公開します。dart:ui API
  • エンジンの機能を使用して特定のプラットフォームと統合します。エンベッダー API

埋め込み者(ソースコード)

  • 基盤となるオペレーティング システムと連携します レンダリング サーフェスなどのサービスにアクセスするため、 アクセシビリティと入力。
  • イベントループを管理します。
  • 暴露するプラットフォーム固有の APIEmbedder をアプリに統合します。

ランナー

  • プラットフォーム固有のコンポーネントによって公開される部分を構成します。 ターゲット プラットフォームで実行可能なアプリ パッケージへの Embedder の API。
  • によって生成されたアプリ テンプレートの一部flutter create、 アプリ開発者が所有。

リアクティブなユーザーインターフェイス

表面的には、 flutterはリアクティブな疑似宣言型 UI フレームワーク、 開発者はアプリケーションの状態からインターフェイスへのマッピングを提供します。 状態に戻り、フレームワークは実行時にインターフェイスを更新するタスクを引き受けます。 アプリケーションの状態が変化したとき。このモデルは以下からインスピレーションを受けています来た仕事 Facebook から独自の React を提供 フレームワーク、 これには、多くの伝統的な設計原則の再考が含まれます。

ほとんどの従来の UI フレームワークでは、ユーザー インターフェイスの初期状態は次のとおりです。 一度記述され、実行時にユーザーコードによって個別に更新されます。 イベントに。このアプローチの課題の 1 つは、アプリケーションが成長するにつれて、 複雑なので、開発者は状態の変化がどのようにカスケードするかを認識する必要があります。 UI全体にわたって。たとえば、次の UI について考えてみましょう。

Color picker dialog

カラーボックス、色相など、状態を変更できる場所はたくさんあります。 スライダー、ラジオボタン。ユーザーが UI を操作する際には、変更を加える必要があります。 あらゆる場所に反映されます。さらに悪いことに、注意を払わない限り、 ユーザーインターフェイスの一部が、一見無関係に見える波及効果を引き起こす可能性があります コードの断片。

これに対する 1 つの解決策は、データ変更を コントローラー経由でモデルを作成し、モデルが新しい状態をビューにプッシュします。 コントローラー経由で。ただし、これにも問題があります。 UI 要素の更新は 2 つの別々の手順であるため、簡単に同期が失われる可能性があります。

Flutter は、他のリアクティブ フレームワークと同様に、別のアプローチを採用しています。 この問題は、ユーザー インターフェイスをその基盤から明示的に切り離すことで解決されます。 州。 React スタイルの API を使用すると、UI の説明を作成するだけで済みます。 フレームワークは、その 1 つの構成を使用して、作成および/または 必要に応じてユーザー インターフェイスを更新します。

Flutter では、ウィジェット (React のコンポーネントに似ています) は不変で表されます。 オブジェクトのツリーを構成するために使用されるクラス。これらのウィジェットは次の目的で使用されます。 レイアウト用のオブジェクトの別のツリーを管理し、それを使用してレイアウトを管理します。 合成用のオブジェクトの別のツリー。 Flutter は、その核となる一連の ツリーの変更された部分を効率的に歩き、ツリーを変換するためのメカニズム オブジェクトをオブジェクトの下位レベルのツリーに変換し、変更をオブジェクト全体に伝播します。 これらの木々。

ウィジェットは、build()方法、これは 状態を UI に変換する関数です。

UI = f(state)

build()このメソッドは設計上実行が速く、サイドが発生しないようにする必要があります。 エフェクトを追加し、必要なときにいつでもフレームワークによって呼び出せるようにします (潜在的に レンダリングされたフレームごとに 1 回)。

このアプローチは、言語ランタイムの特定の特性に依存しています ( 特に、高速なオブジェクトのインスタンス化と削除)。幸運、ダーツは これに特に適しています タスク。

ウィジェット

前述したように、Flutter はウィジェットを構成単位として重視します。ウィジェットは Flutter アプリのユーザー インターフェイスの構成要素であり、各ウィジェットは ユーザーインターフェイスの一部の不変宣言。

ウィジェットは、構成に基づいて階層を形成します。各ウィジェットはその内部にネストされます 親に接続し、親からコンテキストを受信できます。この構造には、すべての機能が搭載されています。 ルート ウィジェット (通常は Flutter アプリをホストするコンテナ) までMaterialAppまたCupertinoApp)、この簡単な例が示すように:

56c586f7-120d-4c83-a006-e291​​344af768

前述のコードでは、インスタンス化されたクラスはすべてウィジェットです。

アプリはイベント(ユーザーのイベントなど)に応じてユーザー インターフェイスを更新します。 インタラクション)、フレームワークに階層内のウィジェットを置き換えるよう指示することで、 別のウィジェット。次に、フレームワークは新しいウィジェットと古いウィジェットを比較し、 ユーザーインターフェイスを効率的に更新します。

Flutter は、各 UI コントロールを延期するのではなく、独自の実装を持っています。 システムによって提供されるもの: たとえば、純粋なダーツ 実装両方のiOSトグル コントロールそしてその1 つのAndroid相当。

このアプローチには、次のようないくつかの利点があります。

  • 無制限の拡張性を提供します。のバリアントを必要とする開発者 スイッチ制御は任意の方法で作成でき、これに限定されません。 OSが提供する拡張ポイント。
  • Flutter を合成できるようにすることで、重大なパフォーマンスのボトルネックを回避します。 Flutter 間を行ったり来たりすることなく、シーン全体を一度に実行できます。 コードとプラットフォームコード。
  • アプリケーションの動作をオペレーティング システムの依存関係から切り離します。の アプリケーションは、OS が異なっていても、OS のすべてのバージョンで同じように見え、同じように感じられます。 コントロールの実装を変更しました。

構成

ウィジェットは通常、他の多くの小さな単一目的のウィジェットで構成されます。 組み合わせて強力な効果を生み出します。

可能な限り、デザインコンセプトの数は最小限に抑えられますが、 総語彙数が多くなる可能性があります。たとえば、ウィジェットレイヤーでは、 Flutter は同じ中心概念を使用します (Widget) への描画を表します。 画面、レイアウト (配置とサイズ設定)、ユーザーの対話性、状態管理、 テーマ、アニメーション、ナビゲーション。アニメーション層では、一対の概念、AnimationTweens、デザインスペースの大部分をカバーします。レンダリング中 層、RenderObjectは、レイアウト、ペイント、ヒット テスト、および アクセシビリティ。これらのそれぞれのケースで、対応する語彙は次のようになります。 大きい: 何百ものウィジェットとレンダリング オブジェクトがあり、数十もの アニメーションとトゥイーンのタイプ。

可能性を最大限に高めるために、クラス階層は意図的に浅く広くされています。 組み合わせの数。それぞれが 1 つの機能を実行する小さな構成可能なウィジェットに焦点を当てています。 まあまあ。コア機能は抽象的であり、パディングなどの基本的な機能も含まれています アライメントは構築されるのではなく、別個のコンポーネントとして実装されます。 核心に。 (これは、より伝統的な API とは対照的です。 パディングなどは、すべてのレイアウト コンポーネントの共通コアに組み込まれています。) たとえば、ウィッジを中央に配置するには概念的な調整ではなく、Align財産、 あなたはそれを包みますCenterウィジェット。

パディング、配置、行、列、グリッド用のウィジェットがあります。これらのレイアウトは ウィジェットには、それ自体の視覚的表現がありません。代わりに、彼らの唯一の 目的は、別のウィジェットのレイアウトの一部を制御することです。 flutterも には、この構成アプローチを利用するユーティリティ ウィジェットが含まれています。

例えば、Container、 一般的に使用されるウィジェットは、レイアウトを担当する複数のウィジェットで構成されます。 ペイント、位置決め、サイズ調整。具体的には、コンテナは次のもので構成されています。LimitedBoxConstrainedBoxAlignPaddingDecoratedBox、 とTransformウィジェット、あなたと同じように ソースコードを読めばわかります。 Flutter の特徴は次のとおりです。 ウィジェットのソースをドリルダウンして調べることができます。それで、むしろ サブクラス化よりもContainerカスタマイズされたエフェクトを作成するには、それを合成します や他のウィジェットを斬新な方法で使用することも、単に次を使用して新しいウィジェットを作成することもできます。Containerインスピレーションとして。

ウィジェットの構築

前述したように、ウィジェットの視覚的表現は次のようにして決定します。 をオーバーライドするbuild()に機能する 新しい要素ツリーを返します。このツリーは、ウィジェットのユーザーの一部を表します より具体的にはインターフェース。たとえば、ツールバー ウィジェットには を返すビルド関数水平 レイアウトいくつかの文章と様々 ボタン。必要に応じて、 フレームワークは、ツリーが完全に完成するまで、再帰的に各ウィジェットの構築を要求します。 によって説明されましたコンクリートレンダリング可能 オブジェクト。の 次に、フレームワークがレンダリング可能なオブジェクトをつなぎ合わせてレンダリング可能なオブジェクトにします。 木。

ウィジェットのビルド関数には副作用があってはなりません。関数のたびに 構築するよう求められた場合、ウィジェットはウィジェットの新しいツリーを返す必要があります1、ウィジェットが以前に返したものには関係ありません。の フレームワークは、どのビルド メソッドが必要かを決定するための重労働を実行します。 レンダー オブジェクト ツリーに基づいて呼び出されます (後で詳しく説明します)。もっと このプロセスに関する情報は、インサイド flutter トピック

レンダリングされた各フレームで、Flutter は UI の部分だけを再作成できます。 そのウィジェットを呼び出すことによって状態が変更されましたbuild()方法。したがって、それは ビルド メソッドがすぐに返されることが重要であり、重い計算作業が必要になる 何らかの非同期方法で実行し、状態の一部として保存する必要があります。 ビルドメソッドで使用されます。

この自動化された比較は比較的単純なアプローチではありますが、 効果的で、高性能でインタラクティブなアプリを実現します。そして、そのデザインは、 build 関数は、ウィジェットが何であるかを宣言することに重点を置くことでコードを簡素化します。 ユーザー インターフェイスを 1 から更新するという複雑さではなく、 別の状態にします。

ウィジェットの状態

このフレームワークでは、ウィジェットの 2 つの主要なクラスが導入されています。ステートフルステートレスウィジェット。

多くのウィジェットには変更可能な状態がありません。変更されるプロパティがありません。 時間の経過とともに (アイコンやラベルなど)。これらのウィジェットのサブクラスStatelessWidget

ただし、ウィジェットの固有の特性をユーザーに基づいて変更する必要がある場合は、 相互作用またはその他の要因により、そのウィジェットはステートフル。たとえば、 ウィジェットには、ユーザーがボタンをタップするたびに増加するカウンターがあり、 カウンタの値はそのウィジェットの状態です。その値が変化すると、 UI の一部を更新するにはウィジェットを再構築する必要があります。これらのウィジェットのサブクラスStatefulWidget、 と (ウィジェット自体は不変であるため) 変更可能な状態を別の場所に保存します。 サブクラス化するクラスStateStatefulWidgetビルドメソッドがありません。代わりに、ユーザー インターフェイスは次のとおりです。 彼らを通じて構築されたState物体。

変異するたびに、ダダ960c-213c-491f-90ba-a0fa5a5c065aオブジェクト (たとえば、カウンターをインクリメントすることによって)、 電話しなければなりませんsetState()を呼び出してフレームワークにユーザー インターフェイスを更新するよう通知します。Stateの ビルドメソッドを再度実行します。

個別のステート オブジェクトとウィジェット オブジェクトを持つことで、他のウィジェットは両方のステートレスを処理できるようになります。 とステートフル ウィジェットをまったく同じ方法で作成できます。 負けている状態。子供の状態を維持するために子供にしがみつく必要はなく、 親はいつでも子のインスタンスを失うことなく新しいインスタンスを作成できます。 子供の永続的な状態。フレームワークは、検索と再利用のすべての作業を実行します。 必要に応じて、既存の状態オブジェクト。

状態管理

では、多くのウィジェットに状態を含めることができる場合、状態はどのように管理され、渡されるのでしょうか? システム?

他のクラスと同様に、ウィジェット内でコンストラクターを使用してそのクラスを初期化できます。 データなので、build()このメソッドは、子ウィジェットが確実にインスタンス化されるようにすることができます。 必要なデータを使用して:

@override
Widget build(BuildContext context) {
   return ContentWidget(importantState);
}

ただし、ウィジェット ツリーが深くなると、状態情報がウィジェット ツリーの上下に受け渡されます。 ツリー階層が複雑になります。したがって、3 番目のウィジェット タイプは、InheritedWidget、 は、共有祖先からデータを取得する簡単な方法を提供します。使用できますInheritedWidget共通の祖先をラップする状態ウィジェットを作成するには、 この例に示すように、ウィジェット ツリー:

Inherited widgets

いつでも、ExamWidgetまたGradeWidgetオブジェクトにはデータが必要ですStudentState、次のようなコマンドでアクセスできるようになります。

final studentState = StudentState.of(context);

of(context)呼び出しはビルド コンテキスト (現在のウィジェットへのハンドル) を受け取ります。 場所)を返します最も近い祖先 木と一致するStudentStateタイプ。InheritedWidgetも提供していますupdateShouldNotify()Flutter がこのメソッドを呼び出して、状態が 変更は、それを使用する子ウィジェットの再構築をトリガーする必要があります。

Flutter自体が使用するInheritedWidget~の枠組みの一部として広範囲に アプリケーションの共有状態などビジュアルテーマ、これには以下が含まれます色や種類などのプロパティ スタイルそれは アプリケーション全体に浸透します。のMaterialApp build()メソッド挿入 構築時にツリー内のテーマが作成され、階層のさらに深いところにウィジェットが追加されます。 を使用できます.of()関連するテーマ データを検索するメソッド。例:

Container(
  color: Theme.of(context).secondaryHeaderColor,
  child: Text(
    'Text with a background color',
    style: Theme.of(context).textTheme.titleLarge,
  ),
);

このアプローチは次の目的にも使用されますナビゲーターを提供します。 ページルーティング。とメディアクエリを提供します。 向き、寸法、明るさなどの画面の指標にアクセスします。

アプリケーションが成長するにつれて、より高度な状態管理アプローチが必要になり、 ステートフルなウィジェットを作成して使用する儀式がより魅力的になります。多くの Flutter アプリは次のようなユーティリティ パッケージを使用します。プロバイダー、ラッパーを提供しますInheritedWidget。 Flutter の階層化アーキテクチャでは、代替手段も可能になります 状態を UI に変換するためのアプローチ。 flutterフックパッケージ。

レンダリングとレイアウト

このセクションでは、レンダリング パイプラインについて説明します。これは、 Flutter は、ウィジェットの階層をペイントされた実際のピクセルに変換します。 スクリーンに。

Flutterのレンダリングモデル

Flutter がクロスプラットフォーム フレームワークであるなら、どうやってそれができるのかと疑問に思われるかもしれません。 単一プラットフォームのフレームワークと同等のパフォーマンスを提供しますか?

伝統的な方法について考えることから始めると有益です。 Androidアプリは動作します。絵を描くとき、 まず Android フレームワークの Java コードを呼び出します。 Android システム ライブラリは次のコンポーネントを提供します。 自分自身を引き寄せる責任があるCanvas物体、 Android でレンダリングできるものスキア、 C/C++ で書かれたグラフィックス エンジン。 CPU または GPU がデバイス上の描画を完了するため、 またインペラ(現在、旗の後ろでプレビュー中です)。

クロスプラットフォームのフレームワーク通常創造することで働く 基礎となるネイティブの上の抽象化レイヤー Android と iOS の UI ライブラリは、 各プラットフォームの表現の不一致。 アプリのコードは多くの場合、JavaScript などのインタープリタ型言語で記述されます。 次に、Java ベースのコンポーネントと対話する必要があります。 UI を表示するための Android または Objective-C ベースの iOS システム ライブラリ。 これらすべてにより、重大なオーバーヘッドが追加される可能性があります。 特にたくさんあるところでは UI とアプリのロジック間の対話。

対照的に、Flutter はそれらの抽象化を最小限に抑えます。 システム UI ウィジェット ライブラリを優先してバイパスします 独自のウィジェットセットの。絵を描くダーツコード Flutter のビジュアルはネイティブ コードにコンパイルされ、 これはレンダリングに Skia (将来的には Impeller) を使用します。 Flutter は独自の Skia コピーをエンジンの一部として埋め込みます。 開発者がアプリをアップグレードして維持できるようにする 最新のパフォーマンス改善により更新されました 携帯電話が新しい Android バージョンに更新されていない場合でも。 trも同じです他のネイティブ プラットフォーム上の Flutter の場合、 Windows や macOS など。

ユーザー入力から GPU まで

Flutter がレンダリング パイプラインに適用する最も重要な原則は次のとおりです。シンプルは速い。 Flutter には、データがどのように流れるかについての簡単なパイプラインがあります。 次のシーケンス図に示すように、システムは次のようになります。

Render pipeline sequencing
diagram

これらのフェーズのいくつかをさらに詳しく見てみましょう。

構築: ウィジェットから要素まで

ウィジェット階層を示す次のコード部分を考えてみましょう。

Container(
  color: Colors.blue,
  child: Row(
    children: [
      Image.network('https://www.example.com/1.png'),
      const Text('A'),
    ],
  ),
);

Flutter がこのフラグメントをレンダリングする必要がある場合、build()方法、これは 現在のアプリの状態に基づいて UI をレンダリングするウィジェットのサブツリーを返します。 このプロセス中に、build()メソッドは、次のように新しいウィジェットを導入できます。 状態に応じて必要です。例として、上記のコードでは、 断片、Containerもっているcolorchildプロパティ。見てみると、ソース コードためにContainer、色が null でない場合、ColoredBox色を表す:

if (color != null)
  current = ColoredBox(color: color!, child: current);

同様に、ImageTextウィジェットは次のような子ウィジェットを挿入する場合があります としてRawImageRichTextビルドプロセス中。最終的なウィジェット したがって、次のように、コードが表すものよりも階層が深くなる可能性があります。 場合2:

Render pipeline sequencing
diagram

これは、次のようなデバッグ ツールを使用してツリーを調べるときの理由を説明します。 flutterインスペクター、 の一部 Dart DevTools では、これよりもかなり深い構造が表示されるかもしれません。 元のコードにあります。

ビルドフェーズ中に、Flutter はコードで表現されたウィジェットを 対応する要素ツリー、ウィジェットごとに 1 つの要素があります。各要素 ツリーの特定の場所にあるウィジェットの特定のインスタンスを表します 階層。要素には 2 つの基本的なタイプがあります。

  • ComponentElement、他の要素のホスト。
  • RenderObjectElement、レイアウトまたはペイントに参加する要素 段階。

Render pipeline sequencing
diagram

RenderObjectElementは、ウィジェットのアナログと 根底にあるRenderObjectこれについては後で説明します。

任意のウィジェットの要素は、そのウィジェットを通じて参照できます。BuildContext、 どれの ツリー内のウィジェットの位置へのハンドルです。これはcontextで 関数呼び出しなどTheme.of(context)に供給されます。build()メソッドをパラメータとして指定します。

ウィジェットは、間の親子関係も含めて不変であるため、 ノード、ウィジェット ツリーへの変更 (変更など)Text('A')Text('B')前の例では)、ウィジェット オブジェクトの新しいセットが生成されます。 戻ってきた。しかし、それは基礎となる表現を再構築する必要があるという意味ではありません。 要素ツリーはフレーム間で永続的であるため、 重要なパフォーマンスの役割を果たし、Flutter がウィジェット階層が存在するかのように動作できるようにします。 基礎となる表現をキャッシュしながら完全に使い捨て可能です。歩くだけで Flutter は、変更されたウィジェットを通じて、 再構成が必要な要素ツリー。

レイアウトとレンダリング

ウィジェットを 1 つだけ描画する珍しいアプリケーションになります。重要な部分 したがって、UI フレームワークの最大の特徴は、階層を効率的にレイアウトする機能です。 ウィジェットの作成前に各要素のサイズと位置を決定します。 画面上にレンダリングされます。

レンダー ツリー内のすべてのノードの基本クラスは次のとおりです。RenderObject、 どれの レイアウトと描画のための抽象モデルを定義します。これは非常に一般的なことです。 固定数の次元やデカルト座標さえも保証しません システム (デモンストレーション:この極座標の例 システム)。各RenderObject親のことは知っていますが、子供たちのこと以外はほとんど知りません。 方法訪問それらとその制約。これにより、RenderObjectと さまざまなユースケースを処理できる十分な抽象化。

ビルドフェーズ中に、Flutter は次のオブジェクトを継承するオブジェクトを作成または更新します。RenderObjectそれぞれにRenderObjectElement要素ツリー内。RenderObjectはプリミティブです:RenderParagraphテキストをレンダリングし、RenderImageレンダリングする 画像と、RenderTransform子をペイントする前に変換を適用します。

Differences between the widgets hierarchy and the element and render
trees

ほとんどの Flutter ウィジェットは、RenderBoxを表すサブクラスRenderObject2D の固定サイズ デカルト空間。RenderBoxの基礎を提供しますボックス制約モデル、 各ウィジェットの幅と高さの最小値と最大値を設定する レンダリングされました。

レイアウトを実行するために、Flutter は深さ優先のトラバーサルでレンダー ツリーを探索し、サイズ制約を継承します親から子へ。サイズを決定する際には、 子供しなければならない親によって与えられた制約を尊重します。子供 までに応答するサイズを逃す制約内の親オブジェクトに 親が設立し​​た。

Constraints go down, sizes go
up

ツリー内のこの 1 つのウォークスルーの終わりには、すべてのオブジェクトが定義されたサイズになります。 親の制約内にあり、呼び出してペイントする準備ができています。paint()方法。

ボックス制約モデルは、オブジェクトをレイアウトする方法として非常に強力です。の上)時間:

  • 親は最大値と最小値を設定することで子オブジェクトのサイズを指定できます。 同じ値に制約します。たとえば、 電話アプリは、その子を画面のサイズに制限します。 (子供でもできます) そのスペースの使い方を選択します。たとえば、必要なものを中心に置くだけかもしれません。 指定された制約内でレンダリングしたい場合。)
  • 親は子の幅を指定できますが、子には柔軟性を与えます。 高さ (または高さを指定しますが、幅に関しては柔軟に対応します)。実際の例 フロー テキストです。水平方向の制約に適合する必要があるかもしれませんが、内容は異なります。 テキストの量に応じて縦方向に。

このモデルは、子オブジェクトがどのくらいのスペースを持っているかを知る必要がある場合でも機能します。 コンテンツをどのようにレンダリングするかを決定できます。を使用することで、LayoutBuilderウィジェット、 子オブジェクトは、渡された制約を検査し、それらを使用して次のことを行うことができます。 それらをどのように使用するかを決定します。たとえば、次のようになります。

Widget build(BuildContext context) {
  return LayoutBuilder(
    builder: (context, constraints) {
      if (constraints.maxWidth < 600) {
        return const OneColumnLayout();
      } else {
        return const TwoColumnLayout();
      }
    },
  );
}

制約とレイアウト システム、および作業内容に関する詳細情報 例は、次の場所にあります。理解 制約トピック。

すべての根源RenderObjectsはRenderView、合計を表します レンダーツリーの出力。プラットフォームが新しいフレームのレンダリングを要求するとき (たとえば、仮想同期それとも テクスチャの解凍/アップロードが完了すると、呼び出しが行われます。compositeFrame()メソッドの一部です。RenderViewルートにあるオブジェクト レンダーツリーの。これにより、SceneBuilderの更新をトリガーするには、 シーン。シーンが完了すると、RenderViewオブジェクトは合成されたものを渡します シーンへWindow.render()のメソッドe8632552-2c2a-4eb1-bda8-44712​​a18dfd7に制御を渡します。 それをレンダリングするための GPU。

パイプラインの構成およびラスタライズ段階の詳細については、次のとおりです。 この概要記事の範囲を超えていますが、詳細についてはこちらをご覧ください。この Flutter レンダリングに関する説明では、 パイプライン。

プラットフォームの組み込み

これまで見てきたように、同等の OS ウィジェットに変換されるのではなく、 Flutter ユーザー インターフェイスは、Flutter によって構築、レイアウト、合成、描画されます。 自体。テクスチャの取得とアプリへの参加の仕組み 基盤となるオペレーティング システムのライフサイクルは、環境に応じて必然的に異なります。 そのプラットフォーム特有の懸念事項。このエンジンはプラットフォームに依存せず、安定した ABI (アプリケーション バイナリ) インターフェース)を提供するプラットフォームエンベッダーFlutter をセットアップして使用する方法を説明します。

プラットフォーム エンベッダーは、すべての Flutter をホストするネイティブ OS アプリケーションです。 コンテンツを保存し、ホスト オペレーティング システムと Flutter の間の接着剤として機能します。 Flutter アプリを起動すると、エンベッダーはエントリポイントを提供し、初期化します。 Flutter エンジン、UI とラスタリング用のスレッドを取得し、テクスチャを作成します Flutter が書き込むことができるもの。エンベダーはアプリに対しても責任を負います 入力ジェスチャ (マウス、キーボード、タッチなど)、ウィンドウを含むライフサイクル サイズ設定、スレッド管理、プラットフォームメッセージ。 Flutterにはプラットフォームが含まれています Android、iOS、Windows、macOS、Linux 用のエンベッダー。を作成することもできます カスタム プラットフォーム エンベッダー、次のようにこれはうまくいきました 例リモートをサポートする VNC スタイルのフレームバッファを通じてセッションを flutterするか、この実際に動作した例は、 ラズベリーパイ。

各プラットフォームには独自の API と制約のセットがあります。簡単な説明 プラットフォーム固有の注意事項:

  • iOS および macOS では、Flutter はエンベッダーにUIViewControllerまたNSViewController、 それぞれ。プラットフォーム エンベッダーは、FlutterEngine、Dart VM と Flutter のホストとして機能します。 ランタイム、およびFlutterViewControllerに付属します。FlutterEngineUIKit または Cocoa 入力イベントを Flutter に渡し、フレームを表示します。 によってレンダリングされたFlutterEngineMetal または OpenGL を使用します。
  • Android では、Flutter はデフォルトで、Activity。 ビューはによって制御されますFlutterView、 これは、Flutter コンテンツをビューまたはテクスチャとしてレンダリングします。 Flutter コンテンツの構成と Z オーダーの要件。
  • Windows では、Flutter は従来の Win32 アプリでホストされ、コンテンツは を使用してレンダリング角度、 OpenGL API 呼び出しを DirectX 11 相当のものに変換するライブラリ。

他のコードとの統合

Flutter は、さまざまな相互運用性メカニズムを提供します。 Kotlin や Swift などの言語で書かれたコードや API にアクセスし、 ネイティブ C ベース API、Flutter アプリへのネイティブ コントロールの埋め込み、または埋め込み 既存のアプリケーションで flutterします。

プラットフォームチャネル

モバイル アプリとデスクトップ アプリの場合、Flutter を使用してカスタム コードを呼び出すことができます。 あるプラットフォームチャンネル、これはユーザー間で通信するためのメカニズムです。 Dart コードとホスト アプリのプラットフォーム固有のコード。共通のものを作ることで、 チャネル(名前とコーデックをカプセル化)、メッセージを送受信できます Dart と Kotlin などの言語で書かれたプラットフォーム コンポーネントの間 迅速。データは次のような Dart タイプからシリアル化されます。Map標準フォーマットに変換し、 その後、Kotlin での同等の表現にデシリアライズされます (例:HashMap) または Swift (など)Dictionary)。

How platform channels allow Flutter to communicate with host
code

以下は、受信側への Dart 呼び出しの短いプラットフォーム チャネルの例です。 Kotlin (Android) または Swift (iOS) のイベント ハンドラー:

// Dart side
const channel = MethodChannel('foo');
final String greeting = await channel.invokeMethod('bar', 'world');
print(greeting);
// Android (Kotlin)
val channel = MethodChannel(flutterView, "foo")
channel.setMethodCallHandler { call, result ->
  when (call.method) {
    "bar" -> result.success("Hello, ${call.arguments}")
    else -> result.notImplemented()
  }
}
// iOS (Swift)
let channel = FlutterMethodChannel(name: "foo", binaryMessenger: flutterView)
channel.setMethodCallHandler {
  (call: FlutterMethodCall, result: FlutterResult) -> Void in
  switch (call.method) {
    case "bar": result("Hello, \(call.arguments as! String)")
    default: result(FlutterMethodNotImplemented)
  }
}

macOS の例を含む、プラットフォーム チャネルの使用例はさらに次のとおりです。 で見つかります flutter/プラグインリポジトリ3。もあります何千ものプラグイン すでに利用可能です多くの一般的なものをカバーする Flutter 用 Firebase から広告、カメラなどのデバイス ハードウェアに至るまでのシナリオ ブルートゥース。

外部関数インターフェース

C ベースの API (C ベースの API で記述されたコード用に生成できる API を含む) の場合 Rust や Go などの最新言語、Dart はバインドのための直接メカニズムを提供します を使用してネイティブ コードに変換するdart:ffi図書館。外部関数インターフェース (FFI) モデルは、プラットフォーム チャネルよりも大幅に高速になる可能性があります。 データを渡すにはシリアル化が必要です。代わりに、Dart ランタイムは、 Dart オブジェクトによってバックアップされるヒープにメモリを割り当てて、 静的または動的にリンクされたライブラリへの呼び出し。 FFI はすべての人が利用できます Web 以外のプラットフォームでは、jsパッケージ同等の目的を果たします。

FFI を使用するには、typedefDart メソッドとアンマネージ メソッドのそれぞれについて 署名を作成し、それらの間でマッピングするように Dart VM に指示します。例として、 従来の Win32 を呼び出すコードの一部を次に示します。MessageBox()API:

import 'dart:ffi';
import 'package:ffi/ffi.dart'; // contains .toNativeUtf16() extension method

typedef MessageBoxNative = Int32 Function(
  IntPtr hWnd,
  Pointer<Utf16> lpText,
  Pointer<Utf16> lpCaption,
  Int32 uType,
);

typedef MessageBoxDart = int Function(
  int hWnd,
  Pointer<Utf16> lpText,
  Pointer<Utf16> lpCaption,
  int uType,
);

void exampleFfi() {
  final user32 = DynamicLibrary.open('user32.dll');
  final messageBox =
      user32.lookupFunction<MessageBoxNative, MessageBoxDart>('MessageBoxW');

  final result = messageBox(
    0, // No owner window
    'Test message'.toNativeUtf16(), // Message
    'Window caption'.toNativeUtf16(), // Window title
    0, // OK button only
  );
}

Flutter アプリでのネイティブ コントロールのレンダリング

Flutter コンテンツはテクスチャに描画され、そのウィジェット ツリー全体が 内部では Android ビューのようなものが存在する場所はありません Flutter の内部モデルまたはレンダリングは Flutter ウィジェット内にインターリーブされます。それは 既存のプラットフォーム コンポーネントを含めたい開発者にとっての問題 ブラウザ コントロールなどの Flutter アプリ内で。

Flutter は、プラットフォーム ビュー ウィジェットを導入することでこれを解決します。 (AndroidViewUiKitView) これにより、この種のコンテンツを各プラットフォームに埋め込むことができます。プラットフォーム ビューは次のとおりです。 他の Flutter コンテンツと統合4。それぞれの これらのウィジェットは、基礎となるオペレーティング システムへの仲介者として機能します。ために たとえば、Android では、AndroidView次の 3 つの主要な機能を果たします。

  • ネイティブ ビューによってレンダリングされたグラフィックス テクスチャのコピーを作成し、 Flutter でレンダリングされたサーフェスの一部として合成するために Flutter に提示します。 フレームを塗装するたびに。
  • ヒット テストと入力ジェスチャに応答し、それらを 同等のネイティブ入力。
  • アクセシビリティ ツリーの類似物を作成し、コマンドと ネイティブ層と Flutter 層の間の応答。

必然的に、これに関連して一定量のオーバーヘッドが発生します。 同期。したがって、一般に、このアプローチは複雑な場合に最も適しています。 Flutter での再実装が現実的ではない Google マップのようなコントロール。

通常、Flutter アプリはこれらのウィジェットをインスタンス化します。build()メソッドベースの プラットフォームテスト中。例として、google_maps_flutterプラグイン:

if (defaultTargetPlatform == TargetPlatform.android) {
  return AndroidView(
    viewType: 'plugins.flutter.io/google_maps',
    onPlatformViewCreated: onPlatformViewCreated,
    gestureRecognizers: gestureRecognizers,
    creationParams: creationParams,
    creationParamsCodec: const StandardMessageCodec(),
  );
} else if (defaultTargetPlatform == TargetPlatform.iOS) {
  return UiKitView(
    viewType: 'plugins.flutter.io/google_maps',
    onPlatformViewCreated: onPlatformViewCreated,
    gestureRecognizers: gestureRecognizers,
    creationParams: creationParams,
    creationParamsCodec: const StandardMessageCodec(),
  );
}
return Text(
    '$defaultTargetPlatform is not yet supported by the maps plugin');

基盤となるネイティブ コードとの通信AndroidViewまたUiKitView通常、前述したように、プラットフォーム チャネル メカニズムを使用して発生します。

現時点では、プラットフォーム ビューはデスクトップ プラットフォームでは利用できませんが、これは アーキテクチャ上の制限ではありません。将来的にサポートが追加される可能性があります。

親アプリでの Flutter コンテンツのホスト

前述のシナリオの逆は、Flutter ウィジェットを 既存の Android または iOS アプリ。前のセクションで説明したように、新しく作成された モバイル デバイス上で実行されている Flutter アプリは Android アクティビティまたは iOS でホストされていますUIViewController。 Flutter コンテンツは既存の Android に埋め込むことができます。 同じ埋め込み API を使用する iOS アプリ。

Flutter モジュール テンプレートは、簡単に埋め込めるように設計されています。どちらかを埋め込むことができます 既存の Gradle または Xcode ビルド定義へのソース依存関係として、または Android アーカイブまたは iOS フレームワーク バイナリにコンパイルして使用できます。 すべての開発者が Flutter をインストールする必要はありません。

Flutter エンジンはロードする必要があるため、初期化に少し時間がかかります。 Flutter 共有ライブラリ、Dart ランタイムの初期化、Dart の作成と実行 レンダリング サーフェスを分離し、UI にアタッチします。 UI の遅延を最小限に抑えるには Flutter コンテンツを提示するときは、Flutter エンジンを初期化するのが最善です アプリの初期化シーケンス全体中、または少なくとも最初の初期化の前に 画面を flutterすることで、ユーザーが最初の表示中に突然停止することがなくなります。 flutterコードが読み込まれます。さらにn、Flutter エンジンを分離すると、 複数の Flutter 画面間で再利用され、関連するメモリ オーバーヘッドを共有します。 必要なライブラリをロードします。

Flutter を既存の Android または iOS アプリにロードする方法の詳細 で見つけることができますロードシーケンス、パフォーマンス、メモリ トピック

Flutter Web のサポート

一般的なアーキテクチャの概念は、Flutter が実行するすべてのプラットフォームに適用されます。 Flutter の Web サポートにはいくつかのユニークな特徴があります。 コメントに値する。

Dart は言語が存在する限りずっと JavaScript にコンパイルされてきました。 開発と実稼働の両方の目的に最適化されたツールチェーンを使用します。多くの 重要なアプリは Dart から JavaScript にコンパイルされ、今日の実稼働環境で実行されます。 含んでいるGoogle 広告用の広告主ツール。 Flutter フレームワークは Dart で書かれているため、JavaScript にコンパイルする必要がありました。 比較的簡単です。

ただし、C++ で書かれた Flutter エンジンは、 とインターフェイスするように設計されています。 Web ブラウザではなく、基盤となるオペレーティング システム。 したがって、別のアプローチが必要になります。 Web 上では、Flutter は次の再実装を提供します。 標準のブラウザ API の上にエンジンを追加します。 現在、次の 2 つのオプションがあります Web 上での Flutter コンテンツのレンダリング: HTML および WebGL。 HTML モードでは、Flutter は HTML、CSS、Canvas、SVG を使用します。 WebGL にレンダリングするために、Flutter は Skia のバージョンを使用します WebAssembly にコンパイルされて呼び出されるキャンバスキット。 HTML モードは最適なコード サイズ特性を提供しますが、CanvasKitへの最速パスを提供します ブラウザのグラフィックススタック、 そして、グラフィックの忠実度がいくらか高くなります。 ネイティブモバイルターゲット5

Web 版のアーキテクチャ層図は次のとおりです。

Flutter web
architecture

おそらく、Flutter が使用される他のプラットフォームと比較した最も顕著な違いは、 run は、Flutter が Dart ランタイムを提供する必要がないことを意味します。その代わり、 Flutter フレームワークは (作成したコードとともに) JavaScript にコンパイルされます。 Dart には言語セマンティクスの違いがほとんどないことも注目に値します。 すべてのモード (JIT と AOT、ネイティブ コンパイルと Web コンパイル)、およびほとんどのモードにわたって 開発者は、そのような違いに遭遇するコード行を決して書きません。

開発中に、Flutter Web は以下を使用します。dartdevcをサポートするコンパイラ インクリメンタルコンパイルのため、ホットリスタートが可能です(ただし、現在は可能ではありません) ホットリロード) アプリの場合。逆に、本番アプリを作成する準備ができたときは、 ウェブの場合、dart2js、ダーツ 高度に最適化された本番用 JavaScript コンパイラーが使用され、Flutter がパッケージ化されます。 コアとフレームワークをアプリケーションとともに圧縮されたソース ファイルにまとめます。 任意の Web サーバーにデプロイできます。コードは単一ファイルまたは分割で提供できます を通じて複数のファイルに分割延期されたインポート。

さらに詳しい情報

Flutter の内部の詳細については、インサイド flutter白書 フレームワークの設計哲学への役立つガイドを提供します。


脚注:

1一方、build関数は新しいツリーを返します。 何かを返せばいいだけです違う新しいものがあれば 組み込む構成。構成が実際に同じであれば、次のことが可能です。 同じウィジェットを返すだけです。

2これは簡単にするために少し簡略化したものです 読む。実際には、ツリーはさらに複雑になる可能性があります。

3Linux と Windows での作業が進行中ですが、 これらのプラットフォームの例は、Flutter デスクトップの埋め込み リポジトリ。 これらのプラットフォームでの開発が成熟に達すると、このコンテンツは 徐々にメインの Flutter リポジトリに移行されました。

4このアプローチにはいくつかの制限があります。 たとえば、透明度はプラットフォーム ビューの場合と同じ方法では合成されません。 他の Flutter ウィジェットでも同様です。

5一例として影があります。 忠実度は多少犠牲になりますが、DOM と同等のプリミティブで近似されます。